import asyncio
import ssl
import json
import logging
import filesolver
import struct


HEADERSIZE = 12
REQUEST, RESPONSE = range(2)
VER = 1

logger = logging.getLogger('file_catcher')
logging.basicConfig(level=logging.DEBUG)


class EchoClientProtocol(asyncio.Protocol):
    def __init__(self, cp):
        self.cp = cp
        self.transport = None
        self._data_buffer = bytes()

    def connection_made(self, transport):
        self.transport = transport
        body = self.cp.get_undone().encode()
        head_pack = struct.pack("!3I", *[VER, len(body), REQUEST])
        message = head_pack + body
        transport.write(message)

    def data_received(self, data):
        asyncio.ensure_future(self._solve_received(data))

    async def _solve_received(self, data):
        self._data_buffer += data
        if len(self._data_buffer) < 12:
            return
        head_pack = struct.unpack('!3I', self._data_buffer[:HEADERSIZE])
        body_size = head_pack[1]
        if len(self._data_buffer) < HEADERSIZE + body_size:
            return
        body = self._data_buffer[HEADERSIZE:HEADERSIZE + body_size]
        self._data_buffer = self._data_buffer[HEADERSIZE + body_size:]

        if not self.cp.content_solve(body):
            self.transport.close()
            return False

        if not self.cp.finish():
            content = self.cp.get_undone().encode()
            head_pack = struct.pack("!3I", *[VER, len(content), REQUEST])
            message = head_pack + content
            self.transport.write(message)
        else:
            self.transport.close()

    def connection_lost(self, exc):
        logger.debug('Connection cloesed')
        # print('Stop the event loop')
        # self.loop.stop()


class CatchProgress(object):
    def __init__(self, filename, metadata=None):
        self.filename = filename
        self.metadata = metadata
        self.get_size = 100
        self.value = ""
        self.blocks = []
        self.pending = []
        pass

    def content_solve(self, content):
        content = json.loads(content.decode())
        if content['statuscode'] == 0:
            if content['op'] == 'getblock':
                self.set_done(content['content'])
            elif content['op'] == 'metadata':
                self.set_metadata(content['content'])
            else:
                logger.warning('Received Unknown: {!r}'.format(content))
            return True
        else:
            return False

    def finish(self):
        if len(self.value) >= self.metadata:
            print('Successfully get file: {!r}'.format(self.filename))
            print(self.value)
        return len(self.value) >= self.metadata

    def set_done(self, value):
        self.value += value

    def has_got(self):
        return len(self.value)

    def set_metadata(self, content):
        self.metadata = int(content)
        self.blocks = list(range(0, self.metadata, self.get_size))

    def get_undone(self):
        if self.metadata is None:
            logger.debug('no metadata')
            return json.dumps({'op': 'metadata', 'file': self.filename, 'offset': 0, 'size': self.get_size})
        else:
            return json.dumps({'op': 'getblock', 'file': self.filename, 'offset': self.has_got(), 'size': self.get_size})


class FileCatcher:
    def __init__(self, parallel, metadata=None):
        self.parallel = parallel
        self.metadata = metadata
        self.coros = []
        # self.loop = asyncio.get_event_loop()
        # TODO

    def sslins(self):
        sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
        sslcontext.load_cert_chain(certfile="cert.pem", keyfile="key.pem")
        return sslcontext

    def add(self, filename, sources):
        cp = CatchProgress(filename, self.metadata)
        loop = asyncio.get_event_loop()
        for source in sources:
            self.coros.append(loop.create_connection(lambda: EchoClientProtocol(cp), source[0], source[1], ssl=self.sslins()))
            if len(self.coros) >= self.parallel:
                break
        loop.run_until_complete(asyncio.wait(self.coros))
        print('Finish')
        pass

    def start(self):
        pass

    def join(self):
        pass

    def cancel(self):
        pass

